#include "BridgeUtils.h"

#include <iostream>
#include <vector>
#include <algorithm>

#include <boost/optional.hpp>
#include <boost/bind.hpp>

#include "Bridge2nativePackageNameAdapter.h"

namespace native_orbitor
{

namespace
{

jmethodID getMethodId( JNIEnv * env, jobject o, const char * name, const char * sig )
{
	jclass oClass = env->GetObjectClass( o );
	assert( oClass );
	jmethodID methodId = env->GetMethodID( oClass, name, sig );
	return methodId;
}

/**
 * returns jobject of type java.lang.Class<? extends typeof(o)>
 * == o.getClass()
 */
jobject getObjectClass( JNIEnv * env, jobject o )
{
	jmethodID getClassMethodId = getMethodId( env, o, "getClass", "()Ljava/lang/Class;" );
	assert( getClassMethodId );
	jobject result = env->CallObjectMethod( o, getClassMethodId );
	assert( result );
	return result;
}

jobjectArray getObjectFieldsJArray( JNIEnv * env, jobject o )
{
	jobject oClass = getObjectClass( env, o );
	jmethodID getFieldsMethodId = getMethodId( env, oClass, "getFields", "()[Ljava/lang/reflect/Field;" );
	assert( getFieldsMethodId );
	jobject notypeFields = env->CallObjectMethod( oClass, getFieldsMethodId );
	assert( notypeFields );
	jobjectArray fields = ( jobjectArray ) notypeFields;
	assert( fields );
	return fields;
}

std::vector< jobject > getObjectFieldsVec( JNIEnv * env, jobject o )
{
	jobjectArray fields = getObjectFieldsJArray( env, o );
	int size = env->GetArrayLength( fields );
	std::vector< jobject > result( size );
	for ( int i = 0; i < size; i++ )
	{
		result[i] = env->GetObjectArrayElement( fields, i );
	}
	return result;
}

const std::string getFieldName( JNIEnv * env, jobject field )
{
	jmethodID getNameMethodId = getMethodId( env, field, "getName", "()Ljava/lang/String;" );
	assert( getNameMethodId );
	jobject notypeFieldName = env->CallObjectMethod( field, getNameMethodId );
	assert( notypeFieldName );
	jstring fieldName = ( jstring ) notypeFieldName;
	assert( fieldName );
	return jstr2stdstr( env, fieldName );
}

const boost::optional< double > jnumeric2double( JNIEnv * env, jobject jnumeric )
{
	jclass jintegerClass = env->FindClass( "java/lang/Integer" );
	assert( jintegerClass );
	jclass jdoubleClass = env->FindClass( "java/lang/Double" );
	assert( jintegerClass );
	if ( env->IsInstanceOf( jnumeric, jintegerClass ) )
	{
		jmethodID intValueMethodId = env->GetMethodID( jintegerClass, "intValue", "()I" );
		assert( intValueMethodId );
		int value = env->CallIntMethod( jnumeric, intValueMethodId );
		return boost::optional< double >( value );
	} else
	if ( env->IsInstanceOf( jnumeric, jdoubleClass ) )
	{
bool implemented = false; assert( implemented );
		return boost::optional< double >();
	}
	return boost::optional< double >();
}

void copyField2map( JNIEnv * env, jobject jobj, jobject field, NativeObjectFieldsMap & outputMap )
{
	assert( field );
	const std::string fieldNameStr = getFieldName( env, field );

	jmethodID fieldGetMethodId = getMethodId( env, field, "get", "(Ljava/lang/Object;)Ljava/lang/Object;" );
	assert( fieldGetMethodId );
	jobject jvalue = env->CallObjectMethod( field, fieldGetMethodId, jobj );
	assert( jvalue );

	const boost::optional< double > value = jnumeric2double( env, jvalue );
	assert( value );
	if ( value )
	{
		outputMap[fieldNameStr] = value.get();
	}
}

}

const std::string jstr2stdstr( JNIEnv * env, jstring s )
{
	jclass bridgeClass = env->FindClass( "jsprintorbitor/bridge2native/BridgeUtils" );
	assert( bridgeClass );
	jmethodID jstr2charArrMethodId = env->GetStaticMethodID( bridgeClass, "jstr2charArray", "(Ljava/lang/String;)[I" );
	assert( jstr2charArrMethodId );
	jobject charIntsJobj = env->CallStaticObjectMethod( bridgeClass, jstr2charArrMethodId, s );
	assert( charIntsJobj );
	jintArray charInts = ( jintArray ) charIntsJobj;
	assert( charInts );
	std::vector< int > charIntsBuf( env->GetArrayLength( charInts ) );
	env->GetIntArrayRegion( charInts, 0, charIntsBuf.size(), & charIntsBuf[0] );
	std::vector< char > fieldNameBuf( charIntsBuf.size() + 1, '\0' );
	std::copy( charIntsBuf.begin(), charIntsBuf.end(), fieldNameBuf.begin() );
	const std::string fieldNameStr( & fieldNameBuf[0] );
	return fieldNameStr;
}

NativeObjectFieldsMap parseGenSettingsMap( JNIEnv * env, jobject grGenSettings )
{
	using namespace GRAPH_SUPPORT_NS::utils;

	stdext::hash_map< std::string, double, Hash4StdString > result;

	const std::vector< jobject > fields = getObjectFieldsVec( env, grGenSettings );
	std::for_each( fields.begin(), fields.end(),
			boost::bind( &copyField2map, env, grGenSettings, _1, boost::ref( result ) ) );

	return result;
}

void jthrow( JNIEnv * env, const std::string & exceptionClassName, const std::string & message )
{
	jstring reason = env->NewStringUTF( message.c_str() );
	assert( reason );
	jclass excClass = env->FindClass( ( std::string( "java/lang/" ) + exceptionClassName ).c_str() );
	assert( excClass );
	jmethodID excConstructor = env->GetMethodID( excClass, "<init>", "(Ljava/lang/String;)V" );
	assert( excConstructor );
	jobject excObj = env->NewObject( excClass, excConstructor, reason );
	jthrowable exception = ( jthrowable ) excObj;
	assert( exception );
	env->Throw( exception );
}

}
